/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2019-04-13     XiaojieFan   the first version
 * 2019-12-04     RenMing      ADD PAGE WRITE and input address can be selected
 * 2022-10-11     GuangweiRen  Delay 2ms after writing one byte
 */

#include <string.h>
#include <stdlib.h>
#include <aos/types.h>

#include "at24cxxx.h"
extern int32_t aos_debug_printf(const char *fmt, ...);
static aos_mutex_t mutex = NULL;

#ifdef PKG_USING_AT24CXX

#define AT24CXX_ADDR (0xA0 >> 1)                      //A0 A1 A2 connect GND

#if (EE_TYPE == AT24C01)
    #define AT24CXX_PAGE_BYTE               8
    #define AT24CXX_MAX_MEM_ADDRESS         128
#elif (EE_TYPE == AT24C02)
    #define AT24CXX_PAGE_BYTE               8
    #define AT24CXX_MAX_MEM_ADDRESS         256
#elif (EE_TYPE == AT24C04)
    #define AT24CXX_PAGE_BYTE               16
    #define AT24CXX_MAX_MEM_ADDRESS         512
#elif (EE_TYPE == AT24C08)
    #define AT24CXX_PAGE_BYTE               16
    #define AT24CXX_MAX_MEM_ADDRESS         1024
#elif (EE_TYPE == AT24C16)
    #define AT24CXX_PAGE_BYTE               16
    #define AT24CXX_MAX_MEM_ADDRESS         2048
#elif (EE_TYPE == AT24C32)
    #define AT24CXX_PAGE_BYTE               32
    #define AT24CXX_MAX_MEM_ADDRESS         4096
#elif (EE_TYPE == AT24C64)
    #define AT24CXX_PAGE_BYTE               32
    #define AT24CXX_MAX_MEM_ADDRESS         8192
#elif (EE_TYPE == AT24C128)
    #define AT24CXX_PAGE_BYTE               64
    #define AT24CXX_MAX_MEM_ADDRESS         16384
#elif (EE_TYPE == AT24C256)
    #define AT24CXX_PAGE_BYTE               64
    #define AT24CXX_MAX_MEM_ADDRESS         32768
#elif (EE_TYPE == AT24C512)
    #define AT24CXX_PAGE_BYTE               128
    #define AT24CXX_MAX_MEM_ADDRESS         65536
#endif

static signed long read_regs(at24cxx_device_t dev, uint16_t memaddr, unsigned char len, unsigned char *buf)
{
    if (rvm_hal_iic_mem_read(dev->i2c, AT24CXX_ADDR | dev->AddrInput, memaddr, (uint16_t)0, buf, len, 0u) == 0) 
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

uint8_t at24cxx_read_one_byte(at24cxx_device_t dev, uint16_t readAddr)
{
    unsigned char buf[3];
    unsigned char temp = 0;
#if (EE_TYPE > AT24C16)
    buf[0] = (uint8_t)(readAddr>>8);
    buf[1] = (uint8_t)readAddr;
#else
    buf[0] = readAddr;
#endif
    read_regs(dev, readAddr, 1, (unsigned char *)&temp);
    return temp;
}

signed long at24cxx_write_one_byte(at24cxx_device_t dev, uint16_t writeAddr, uint8_t dataToWrite)
{
    unsigned char buf[3];
#if (EE_TYPE > AT24C16)
    buf[0] = (uint8_t)(writeAddr>>8);
    buf[1] = (uint8_t)writeAddr;
    buf[2] = dataToWrite;
    if (rvm_hal_iic_master_send(dev->i2c,  AT24CXX_ADDR | dev->AddrInput, (uint8_t *)buf, 3, 100u) == 0)
#else
    buf[0] = writeAddr; //cmd
    buf[1] = dataToWrite;

    if (rvm_hal_iic_master_send(dev->i2c,  AT24CXX_ADDR | dev->AddrInput, (uint8_t *)buf, 2, 100u) == 0)
#endif
    {
        return 0;
    }
    else
    {
        return -1;
    }

}

signed long at24cxx_read_page(at24cxx_device_t dev, uint32_t readAddr, uint8_t *pBuffer, uint16_t numToRead)
{
    uint8_t AddrBuf[2];
    int len;

#if (EE_TYPE > AT24C16)
    AddrBuf[0] = readAddr >> 8;
    AddrBuf[1] = readAddr;
    len = 2;
#else
    AddrBuf[0] = readAddr;
    len = 1;
#endif
    rvm_hal_iic_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, (uint8_t *)AddrBuf, len, 100u);
    rvm_hal_iic_master_recv(dev->i2c, AT24CXX_ADDR | dev->AddrInput, (uint8_t *)pBuffer, numToRead, 100u);
    return 0;
}

signed long at24cxx_write_page(at24cxx_device_t dev, uint32_t wirteAddr, uint8_t *pBuffer, uint16_t numToWrite)
{
    uint8_t AddrBuf[2];
    int len;

#if (EE_TYPE > AT24C16)
    AddrBuf[0] = wirteAddr >> 8;
    AddrBuf[1] = wirteAddr;
    len = 2;
#else
    AddrBuf[0] = wirteAddr;
    len = 1;
#endif
                  
    rvm_hal_iic_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, (uint8_t *)AddrBuf, len, 100u);
    rvm_hal_iic_master_recv(dev->i2c, AT24CXX_ADDR | dev->AddrInput, (uint8_t *)pBuffer, numToWrite, 100u);
    
    return 0;
}

signed long at24cxx_check(at24cxx_device_t dev)
{
    uint8_t temp;
    CHECK_RET_WITH_RET(dev, -1);

    temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
    if (temp == 0x55) return 0;
    else
    {
        at24cxx_write_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1, 0x55);
        aos_msleep(EE_TWR);                 // wait 5ms befor next operation
        temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
        if (temp == 0x55) return 0;
    }
    return 1;
}

/**
 * This function read the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param ReadAddr the start position to read
 * @param pBuffer  the read data store position
 * @param NumToRead
 * @return 0  write ok.
 */
signed long at24cxx_read(at24cxx_device_t dev, uint32_t ReadAddr, uint8_t *pBuffer, uint16_t NumToRead)
{
    signed long result;
    CHECK_RET_WITH_RET(dev, -1);

    if(ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS)
    {
        return 1;
    }

    result = aos_mutex_lock(dev->lock, AOS_NO_WAIT);

    if (result == 0)
    {
        while (NumToRead)
        {
            *pBuffer++ = at24cxx_read_one_byte(dev, ReadAddr++);
            NumToRead--;
        }
    }
    else
    {
        aos_debug_printf("The at24cxx could not respond  at this time. Please try again");
    }
    aos_mutex_unlock(dev->lock);

    return 0;
}

/**
 * This function read the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param ReadAddr the start position to read
 * @param pBuffer  the read data store position
 * @param NumToRead
 * @return 0  write ok.
 */
signed long at24cxx_page_read(at24cxx_device_t dev, uint32_t ReadAddr, uint8_t *pBuffer, uint16_t NumToRead)
{
    signed long result = 0;
    uint16_t pageReadSize = AT24CXX_PAGE_BYTE - ReadAddr % AT24CXX_PAGE_BYTE;

    CHECK_RET_WITH_RET(dev, -1);

    if(ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS)
    {
        return 1;
    }

    result = aos_mutex_lock(dev->lock, AOS_WAIT_FOREVER);
    if(result == 0)
    {
        while (NumToRead)
        {
            if(NumToRead > pageReadSize)
            {
                if(at24cxx_read_page(dev, ReadAddr, pBuffer, pageReadSize))
                {
                    result = 1;
                }

                ReadAddr += pageReadSize;
                pBuffer += pageReadSize;
                NumToRead -= pageReadSize;
                pageReadSize = AT24CXX_PAGE_BYTE;
            }
            else
            {
                if(at24cxx_read_page(dev, ReadAddr, pBuffer, NumToRead))
                {
                    result = 1;
                }
                NumToRead = 0;
            }
        }
    }
    else
    {
        aos_debug_printf("The at24cxx could not respond  at this time. Please try again");
    }

    aos_mutex_unlock(dev->lock);
    return result;
}

/**
 * This function write the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param WriteAddr the start position to write
 * @param pBuffer  the data need to write
 * @param NumToWrite
 * @return 0  write ok.at24cxx_device_t dev
 */
signed long at24cxx_write(at24cxx_device_t dev, uint32_t WriteAddr, uint8_t *pBuffer, uint16_t NumToWrite)
{
    uint16_t i = 0;
    signed long result;
    CHECK_RET_WITH_RET(dev, -1);

    if(WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS)
    {
        return 1;
    }

    result = aos_mutex_lock(dev->lock, AOS_WAIT_FOREVER);
    if (result == 0)
    {
        while (1) //NumToWrite--
        {
            if (at24cxx_write_one_byte(dev, WriteAddr, pBuffer[i]) == 0)
            {
                aos_msleep(2);
                WriteAddr++;
            }
            if (++i == NumToWrite)
            {
                break;
            }
            aos_msleep(EE_TWR);
        }
    }
    else
    {
        aos_debug_printf("The at24cxx could not respond  at this time. Please try again");
    }
    aos_mutex_unlock(dev->lock);

    return 0;
}

/**
 * This function write the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param WriteAddr the start position to write
 * @param pBuffer  the data need to write
 * @param NumToWrite
 * @return 0  write ok.at24cxx_device_t dev
 */
signed long at24cxx_page_write(at24cxx_device_t dev, uint32_t WriteAddr, uint8_t *pBuffer, uint16_t NumToWrite)
{
    signed long result = 0;
    uint16_t pageWriteSize = AT24CXX_PAGE_BYTE - WriteAddr % AT24CXX_PAGE_BYTE;

    CHECK_RET_WITH_RET(dev, -1);

    if(WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS)
    {
        return 1;
    }

    result = aos_mutex_lock(dev->lock, AOS_WAIT_FOREVER);
    if(result == 0)
    {
        while (NumToWrite)
        {
            if(NumToWrite > pageWriteSize)
            {
                if(at24cxx_write_page(dev, WriteAddr, pBuffer, pageWriteSize))
                {
                    result = 1;
                }
                aos_msleep(EE_TWR);    // wait 5ms befor next operation

                WriteAddr += pageWriteSize;
                pBuffer += pageWriteSize;
                NumToWrite -= pageWriteSize;
                pageWriteSize = AT24CXX_PAGE_BYTE;
            }
            else
            {
                if(at24cxx_write_page(dev, WriteAddr, pBuffer, NumToWrite))
                {
                    result = 1;
                }
                aos_msleep(EE_TWR);   // wait 5ms befor next operation

                NumToWrite = 0;
            }
        }
    }
    else
    {
        aos_debug_printf("The at24cxx could not respond  at this time. Please try again");
    }

    aos_mutex_unlock(dev->lock);
    return result;
}

/**
 * This function initializes at24cxx registered device driver
 *
 * @param dev the name of at24cxx device
 *
 * @return the at24cxx device.
 */
at24cxx_device_t at24cxx_init(const char *i2c_bus_name, unsigned int iic_idx, uint8_t AddrInput)
{
    int result;
    at24cxx_device_t dev;
    char filename[8] = {0};
    rvm_hal_iic_config_t config = {0};

    /* AT24Cxx_device allocation space */
    dev = calloc(1, sizeof(struct at24cxx_device));
    if (dev == NULL)
    {
        aos_debug_printf("Can't allocate memory for at24cxx device on '%s' ", i2c_bus_name);
        return NULL;
    }

    sprintf(filename, "iic%d", iic_idx);

    /* Register Device */
    rvm_iic_drv_register(iic_idx);

    /* Get device handle */
    dev->i2c = rvm_hal_iic_open(filename);

    /* Initialize as default parameter */
    rvm_hal_iic_config_default(&config);

    /* Configure device parameters */
    rvm_hal_iic_config(dev->i2c, &config);
    if (dev->i2c == NULL)
    {
        aos_debug_printf("Can't find at24cxx device on '%s' ", i2c_bus_name);
        free(dev);
        return NULL;
    }

    result = aos_mutex_new(&mutex);

    if(result != 0)
    {
        aos_debug_printf("create dynamic mutex failed.\n");
        return NULL;
    }

    dev->lock = &mutex;
    if (dev->lock == NULL)
    {
        aos_debug_printf("Can't create mutex for at24cxx device on '%s' ", i2c_bus_name);
        free(dev);
        return NULL;
    }

    dev->AddrInput = AddrInput;
    return dev;
}

/**
 * This function releases memory and deletes mutex lock
 *
 * @param dev the pointer of device driver structure
 */
int at24cxx_deinit(at24cxx_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);

    aos_mutex_free(dev->lock);

    free(dev);

    return 0;
}

uint8_t TEST_BUFFER[] = "WELCOM TO RTT";
#define SIZE sizeof(TEST_BUFFER)

void at24cxx(int argc, char *argv[])
{
    int id = 0; 
    char* usr_str;
    static at24cxx_device_t dev = NULL;

    if (argc > 1)
    {
        if (!strcmp(argv[1], "probe"))
        {
            if (argc > 2)
            {
                /* initialize the sensor when first probe */

                /* deinit the old device */
                if (dev)
                {
                    at24cxx_deinit(dev);
                }

                int len = strlen(argv[2]);

                if( len > 3)
                {
                    strtok(argv[2],"c");
                    usr_str = strtok(NULL,"c");
                    id = atoi(usr_str);
                }
                dev = at24cxx_init(argv[2], id, atoi(argv[3]));
                
            }
            else
            {
                aos_debug_printf("at24cxx probe <dev_name> <AddrInput> - probe sensor by given name\n");
            }
        }
        else if (!strcmp(argv[1], "read"))
        {
            if (dev)
            {
                uint8_t testbuffer[50];

                /* read the eeprom data */
                at24cxx_read(dev, 0, testbuffer, SIZE);

                aos_debug_printf("read at24cxx : %s\n", testbuffer);

            }
            else
            {
                aos_debug_printf("Please using 'at24cxx probe <dev_name>' first\n");
            }
        }
        else if (!strcmp(argv[1], "write"))
        {
            at24cxx_write(dev, 0, TEST_BUFFER, SIZE);
            aos_debug_printf("write ok\n");
        }
        else if (!strcmp(argv[1], "check"))
        {
            if (at24cxx_check(dev) == 1)
            {
                aos_debug_printf("check faild \n");
            }
        }
        else
        {
            aos_debug_printf("Unknown command. Please enter 'at24cxx0' for help\n");
        }
    }
    else
    {
        aos_debug_printf("Usage:\n");
        aos_debug_printf("at24cxx probe <dev_name> <dev_addr>  - probe eeprom by given name\n");
        aos_debug_printf("at24cxx check                        - check eeprom at24cxx \n");
        aos_debug_printf("at24cxx read                         - read eeprom at24cxx data\n");
        aos_debug_printf("at24cxx write                        - write eeprom at24cxx data\n");

    }
}
ALIOS_CLI_CMD_REGISTER(at24cxx, at24cxx, at24cxx eeprom function);

#endif
